﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.IO;
using Microsoft.SqlServer.Types;
using System.Drawing.Drawing2D;

namespace DataConDemoWebRole.Business
{
    using System.Data.SqlTypes;

    /// <summary>
    /// This will make a single tile for the given type of objects at the given location.
    /// </summary>
    public class TileMaker
    {
        private DataService MyDataService { get; set; }
        private string LayerName { get; set; }
        private string QuadKey { get; set; }
        private bool IsThematic { get; set; }
        private LoadStyle CurrentLayerStyle { get; set; }

        // The following temp variables are used as "out" arguments, 
        // so they must remain primitives rather than properties.
        // current zoom level
        private int m_MapZoomLevel;

        // current X,Y coordinates of tile
        private int m_TileX;
        private int m_TileY;

        // current X,Y coordinates of this tile in pixels
        private int m_NwX;
        private int m_NwY;

        // current bounding box of tile
        private double m_NwLongitude;
        private double m_NwLatitude;
        private double m_SeLongitude;
        private double m_SeLatitude;

        // temp variables used in rendering functions
        private int pixelX;
        private int pixelY;

        public TileMaker(DataService dataService)
        {
            this.MyDataService = dataService;
        }

        #region Helper Functions

        /// <summary>
        /// HemisphereCorrection
        ///     attempts to correct polygons crossing International Dataline
        /// </summary>
        /// <param name="lon"></param>
        /// <param name="lon1"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        private double HemisphereCorrection(double lon, double lon1)
        {
            //truncate polygon to nearest hemisphere boundary
            if ((lon < 0.0 && lon1 > 0.0) || (lon > 0.0 && lon1 < 0.0))
            {
                // crosses hemisphere - use shorter of distances to opposite hemisphere boundaries
                double d1 = Math.Abs(lon1 - 180);
                double d2 = Math.Abs(lon1 - 0.0);
                if (lon1 > 0)
                {
                    if (d1 < d2) lon = 180.0;
                    else lon = 0.0;
                }
                else
                {
                    if (d1 < d2) lon = -180.0;
                    else lon = 0.0;
                }
            }
            return lon;
        }


        /// <summary>
        /// Clips a number to the specified minimum and maximum values.
        /// </summary>
        /// <param name="n">The number to clip.</param>
        /// <param name="minValue">Minimum allowable value.</param>
        /// <param name="maxValue">Maximum allowable value.</param>
        /// <returns>The clipped value.</returns>
        /// <remarks>
        ///     Most helper functions are from MSDN site:
        ///     http://msdn.microsoft.com/en-us/library/bb259689.aspx
        ///</remarks>
        private static double Clip(double n, double minValue, double maxValue)
        {
            return Math.Min(Math.Max(n, minValue), maxValue);
        }



        /// <summary>
        /// Determines the map width and height (in pixels) at a specified level
        /// of detail.
        /// </summary>
        /// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
        /// to 23 (highest detail).</param>
        /// <returns>The map width and height in pixels.</returns>
        public static uint MapSize(int levelOfDetail)
        {
            return (uint)256 << levelOfDetail;
        }



        /// <summary>
        /// Determines the ground resolution (in meters per pixel) at a specified
        /// latitude and level of detail.
        /// </summary>
        /// <param name="latitude">Latitude (in degrees) at which to measure the
        /// ground resolution.</param>
        /// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
        /// to 23 (highest detail).</param>
        /// <returns>The ground resolution, in meters per pixel.</returns>
        public static double GroundResolution(double latitude, int levelOfDetail)
        {
            latitude = Clip(latitude, MinLatitude, MaxLatitude);
            return Math.Cos(latitude * Math.PI / 180) * 2 * Math.PI * EarthRadius / MapSize(levelOfDetail);
        }



        /// <summary>
        /// Determines the map scale at a specified latitude, level of detail,
        /// and screen resolution.
        /// </summary>
        /// <param name="latitude">Latitude (in degrees) at which to measure the
        /// map scale.</param>
        /// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
        /// to 23 (highest detail).</param>
        /// <param name="screenDpi">Resolution of the screen, in dots per inch.</param>
        /// <returns>The map scale, expressed as the denominator N of the ratio 1 : N.</returns>
        public static double MapScale(double latitude, int levelOfDetail, int screenDpi)
        {
            return GroundResolution(latitude, levelOfDetail) * screenDpi / 0.0254;
        }



        /// <summary>
        /// Converts a point from latitude/longitude WGS-84 coordinates (in degrees)
        /// into pixel XY coordinates at a specified level of detail.
        /// </summary>
        /// <param name="latitude">Latitude of the point, in degrees.</param>
        /// <param name="longitude">Longitude of the point, in degrees.</param>
        /// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
        /// to 23 (highest detail).</param>
        /// <param name="pixelX">Output parameter receiving the X coordinate in pixels.</param>
        /// <param name="pixelY">Output parameter receiving the Y coordinate in pixels.</param>
        public static void LatLongToPixelXY(double latitude, double longitude, int levelOfDetail, out int pixelX, out int pixelY)
        {
            latitude = Clip(latitude, MinLatitude, MaxLatitude);
            longitude = Clip(longitude, MinLongitude, MaxLongitude);

            double x = (longitude + 180) / 360;
            double sinLatitude = Math.Sin(latitude * Math.PI / 180);
            double y = 0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI);

            uint mapSize = MapSize(levelOfDetail);
            pixelX = (int)Clip(x * mapSize + 0.5, 0, mapSize - 1);
            pixelY = (int)Clip(y * mapSize + 0.5, 0, mapSize - 1);
        }



        /// <summary>
        /// Converts a pixel from pixel XY coordinates at a specified level of detail
        /// into latitude/longitude WGS-84 coordinates (in degrees).
        /// </summary>
        /// <param name="pixelX">X coordinate of the point, in pixels.</param>
        /// <param name="pixelY">Y coordinates of the point, in pixels.</param>
        /// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
        /// to 23 (highest detail).</param>
        /// <param name="latitude">Output parameter receiving the latitude in degrees.</param>
        /// <param name="longitude">Output parameter receiving the longitude in degrees.</param>
        public static void PixelXYToLatLong(int pixelX, int pixelY, int levelOfDetail, out double latitude, out double longitude)
        {
            double mapSize = MapSize(levelOfDetail);
            double x = (Clip(pixelX, 0, mapSize - 1) / mapSize) - 0.5;
            double y = 0.5 - (Clip(pixelY, 0, mapSize - 1) / mapSize);

            latitude = 90 - 360 * Math.Atan(Math.Exp(-y * 2 * Math.PI)) / Math.PI;
            longitude = 360 * x;
        }

        private const double EarthRadius = 6378137;
        private const double MinLatitude = -85.05112878;
        private const double MaxLatitude = 85.05112878;
        private const double MinLongitude = -180;
        private const double MaxLongitude = 180;

        /// <summary>
        /// Converts pixel XY coordinates into tile XY coordinates of the tile containing
        /// the specified pixel.
        /// </summary>
        /// <param name="pixelX">Pixel X coordinate.</param>
        /// <param name="pixelY">Pixel Y coordinate.</param>
        /// <param name="tileX">Output parameter receiving the tile X coordinate.</param>
        /// <param name="tileY">Output parameter receiving the tile Y coordinate.</param>
        public static void PixelXYToTileXY(int pixelX, int pixelY, out int tileX, out int tileY)
        {
            tileX = pixelX / 256;
            tileY = pixelY / 256;
        }



        /// <summary>
        /// Converts tile XY coordinates into pixel XY coordinates of the upper-left pixel
        /// of the specified tile.
        /// </summary>
        /// <param name="tileX">Tile X coordinate.</param>
        /// <param name="tileY">Tile Y coordinate.</param>
        /// <param name="pixelX">Output parameter receiving the pixel X coordinate.</param>
        /// <param name="pixelY">Output parameter receiving the pixel Y coordinate.</param>
        public static void TileXYToPixelXY(int tileX, int tileY, out int pixelX, out int pixelY)
        {
            pixelX = tileX * 256;
            pixelY = tileY * 256;
        }



        /// <summary>
        /// Converts tile XY coordinates into a QuadKey at a specified level of detail.
        /// </summary>
        /// <param name="tileX">Tile X coordinate.</param>
        /// <param name="tileY">Tile Y coordinate.</param>
        /// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
        /// to 23 (highest detail).</param>
        /// <returns>A string containing the QuadKey.</returns>
        public static string TileXYToQuadKey(int tileX, int tileY, int levelOfDetail)
        {
            StringBuilder quadKey = new StringBuilder();
            for (int i = levelOfDetail; i > 0; i--)
            {
                char digit = '0';
                int mask = 1 << (i - 1);
                if ((tileX & mask) != 0)
                {
                    digit++;
                }
                if ((tileY & mask) != 0)
                {
                    digit++;
                    digit++;
                }
                quadKey.Append(digit);
            }
            return quadKey.ToString();
        }


        /// <summary>
        /// Converts a QuadKey into tile XY coordinates.
        /// </summary>
        /// <param name="quadKey">QuadKey of the tile.</param>
        /// <param name="tileX">Output parameter receiving the tile X coordinate.</param>
        /// <param name="tileY">Output parameter receiving the tile Y coordinate.</param>
        /// <param name="levelOfDetail">Output parameter receiving the level of detail.</param>
        public static void QuadKeyToTileXY(string quadKey, out int tileX, out int tileY, out int levelOfDetail)
        {
            tileX = tileY = 0;
            levelOfDetail = quadKey.Length;
            for (int i = levelOfDetail; i > 0; i--)
            {
                int mask = 1 << (i - 1);
                switch (quadKey[levelOfDetail - i])
                {
                    case '0':
                        break;

                    case '1':
                        tileX |= mask;
                        break;

                    case '2':
                        tileY |= mask;
                        break;

                    case '3':
                        tileX |= mask;
                        tileY |= mask;
                        break;

                    default:
                        throw new ArgumentException("Invalid QuadKey digit sequence.");
                }
            }
        }

        /// <summary>
        /// ColorFromInt
        /// Returns a Color from hex string i.e. #FF00FF00
        /// </summary>
        /// <param name="hex">string hex color with alpha, red, green, blue</param>
        /// <returns>Color</returns>
        private Color ColorFromInt(string hex)
        {
            if (hex.StartsWith("#")) hex = hex.Substring(1);
            int c = int.Parse(hex, NumberStyles.AllowHexSpecifier);
            return Color.FromArgb((byte)((c >> 0x18) & 0xff),
                (byte)((c >> 0x10) & 0xff),
                (byte)((c >> 8) & 0xff),
                (byte)(c & 0xff));
        }


        /// <summary>
        /// Returns a random color
        /// </summary>
        /// <returns></returns>
        public static Color RandomColor()
        {
            Random randomSeed = new Random();
            return Color.FromArgb(
                randomSeed.Next(256),
                randomSeed.Next(256),
                randomSeed.Next(256)
            );
        }


        #endregion



        //8 color values in thematic range
        private static string[] ThematicColors = new string[] { "#FF400000", "#FF804040", "#FFC04000", "#FFC08080", "#FFFF0000", "#FFFF8040", "#FFFFC080", "#FFFFFF80" };

        private Color GetGeoColor(DataService.TileGeo tg)
        {
            if (IsThematic)
            {
                double value = tg.DataValue / CurrentLayerStyle.maxValue;
                for (int i = 1; i < ThematicColors.Length; i++)
                {
                    if (value < (i / (CurrentLayerStyle.skewFactor)))
                    {
                        return ColorFromInt(ThematicColors[i - 1]);
                    }
                }
                return ColorFromInt(ThematicColors[ThematicColors.Length - 1]);
            }
            else
            {
                return CurrentLayerStyle.fill;
            }
        }

        /// <summary>
        /// Get a stream representing a Tile.
        /// </summary>
        /// <param name="layer">name of items to render</param>
        /// <param name="quadkey">key for tile</param>
        /// <param name="thematic">if true, use an attribute to determine the color</param>
        /// <returns>a stream representing a Tile</returns>
        internal System.IO.Stream GetTile(string layer, string quadkey, bool thematic = true)
        {
            this.LayerName = layer;
            this.QuadKey = quadkey;
            IsThematic = thematic;

            //Encoding encoding = new UTF8Encoding();
            Bitmap tileBitmap = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
            Graphics g = Graphics.FromImage(tileBitmap);
            try
            {
                var imageAsMemoryStream = new MemoryStream();
                m_MapZoomLevel = quadkey.Length;

                // calculate a reduction factor based on zoom level
                double reduce = 16000 - (m_MapZoomLevel * 2000);
                reduce = Math.Max(0, reduce);

                QuadKeyToTileXY(quadkey, out m_TileX, out m_TileY, out m_MapZoomLevel);
                TileXYToPixelXY(m_TileX, m_TileY, out m_NwX, out m_NwY);
                PixelXYToLatLong(m_NwX, m_NwY, m_MapZoomLevel, out m_NwLatitude, out m_NwLongitude);
                PixelXYToLatLong(m_NwX + 256, m_NwY + 256, m_MapZoomLevel, out m_SeLatitude, out m_SeLongitude);
                // TODO: should this be a range? Do we really need it?
                if (m_NwLongitude == -180.0) m_NwLongitude = -179.9;

                var box = GeoJsonConvert.GeograhyFromBoundingBoxNwSe(m_NwLatitude, m_NwLongitude, m_SeLatitude, m_SeLongitude);
                List<DataService.TileGeo> geos = null;
                switch (this.LayerName)
                {
                    case DataService.TableName.Counties:
                        CurrentLayerStyle = new LoadStyle(Color.Red, Color.Aqua, 0.25, 2, "AREA", 74.22, 512.0);
                        geos = MyDataService.GetTileGeographiesCounties(box, reduce);
                        break;


                    case DataService.TableName.Countries:
                        CurrentLayerStyle = new LoadStyle(Color.Red, Color.Green, 0.25, 1, "AREA", 6043.6, 128.0);
                        geos = MyDataService.GetTileGeographiesCountries(box, reduce);
                        break;


                    case DataService.TableName.Earthquakes:
                        CurrentLayerStyle = new LoadStyle(Color.Red, Color.Yellow, 1.0, 14, "OTHER_MAG1", 9.24, 10.0);
                        geos = MyDataService.GetTileGeographiesEarthquakes(box, reduce);
                        break;


                    case DataService.TableName.Faults:
                        CurrentLayerStyle = new LoadStyle(Color.Red, Color.Red, 1.0, 2, "ACODE", 6, 8.0);
                        geos = MyDataService.GetTileGeographiesFaults(box, reduce);
                        break;


                    case DataService.TableName.States:
                        CurrentLayerStyle = new LoadStyle(Color.Red, Color.Blue, 0.25, 1, "AREA", 3071704.0, 50.0);
                        geos = MyDataService.GetTileGeographiesStates(box, reduce);
                        break;

                }

                foreach (var tg in geos)
                {
                    Color color = GetGeoColor(tg);
                    var geo = tg.Geography;
                    string table = layer;

                    switch (geo.STGeometryType().ToString().ToUpper())
                    {
                        case "POINT":
                            {
                                RenderPoint(geo, g, color);
                                break;
                            }
                        case "LINESTRING":
                            {
                                RenderLinestring(geo, g, color);
                                break;
                            }
                        case "POLYGON":
                            {
                                RenderPolygon(geo, g, color);
                                break;
                            }
                        case "MULTILINESTRING":
                            {
                                RenderMultiLinestring(geo, g, color);
                                break;
                            }
                        case "MULTIPOLYGON":
                            {
                                RenderMultiPolygon(geo, g, color);
                                break;
                            }
                        case "GEOMETRYCOLLECTION":
                            {
                                RenderGeometryCollection(geo, g, color);
                                break;
                            }
                    }

                }

                tileBitmap.Save(imageAsMemoryStream, ImageFormat.Png);
                imageAsMemoryStream.Position = 0;
                return imageAsMemoryStream;
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error making tile: " + ex.Message);
                throw;
            }
            finally
            {
                tileBitmap.Dispose();
            }
        }


        #region Render methods
        /// <summary>
        /// RenderPoint
        ///     Render a point to Graphics
        /// </summary>
        /// <param name="geo">SqlGeography geo</param>
        /// <param name="g">Graphics used to draw to</param>
        /// <param name="valueFill">Color the fill color style</param>
        private void RenderPoint(SqlGeography geo, Graphics g, Color valueFill)
        {
            double lat = (double)geo.Lat;
            double lon = (double)geo.Long;
            LatLongToPixelXY(lat, lon, m_MapZoomLevel, out pixelX, out pixelY);
            Point cp = new Point(pixelX - m_NwX, pixelY - m_NwY);
            SolidBrush myBrush = new SolidBrush(valueFill);
            int r = CurrentLayerStyle.pointRadius;
            g.FillEllipse(myBrush, new Rectangle(cp.X - r / 2, cp.Y - r / 2, r, r));
        }

        /// <summary>
        /// RenderLinestring
        ///     Render a linestring to Graphics
        /// </summary>
        /// <param name="geo">SqlGeography geo</param>
        /// <param name="table">string layer table</param>
        /// <param name="g">Graphics used to draw to</param>
        /// <param name="valueFill">Color the stroke color style</param>
        private void RenderLinestring(SqlGeography geo, Graphics g, Color valueFill)
        {
            SqlInt32 numPoints = geo.STNumPoints();
            if (numPoints > 1)
            {
                Point[] ptArray = new Point[(int)numPoints];
                double lon1 = 0.0;
                for (int j = 1; j <= numPoints; j++)
                {
                    SqlGeography pointN = geo.STPointN(j);
                    double lat = (double)pointN.Lat;
                    double lon = (double)pointN.Long;
                    if (j > 1)
                    {
                        lon = HemisphereCorrection(lon, lon1);
                    }
                    LatLongToPixelXY(lat, lon, m_MapZoomLevel, out pixelX, out pixelY);
                    ptArray[j - 1] = new Point(pixelX - m_NwX, pixelY - m_NwY);
                    lon1 = lon;
                }
                GraphicsPath linePath = new GraphicsPath();
                linePath.AddLines(ptArray);
                Pen myPen = new Pen(valueFill);
                myPen.Width = 2;
                g.DrawPath(myPen, linePath);
            }
        }

        /// <summary>
        /// RenderPolygon
        ///     Render a polygon to Graphics
        /// </summary>
        /// <param name="geo">SqlGeography geo</param>
        /// <param name="table">string layer table</param>
        /// <param name="g">Graphics used to draw to</param>
        /// <param name="valueFill">Color the fill color style</param>
        private void RenderPolygon(SqlGeography geo, Graphics g, Color valueFill)
        {
            SqlInt32 numRings = geo.NumRings();
            if (numRings > 0)
            {
                Region finalregion = null;

                for (int j = 1; j <= numRings; j++)
                {
                    SqlGeography ringN = geo.RingN(j);
                    SqlInt32 numPoints = ringN.STNumPoints();
                    if (numPoints > 1)
                    {
                        Point[] ptArray = new Point[(int)numPoints];
                        double lon1 = 0.0;
                        for (int k = 1; k <= numPoints; k++)
                        {
                            SqlGeography pointN = ringN.STPointN(k);
                            double lat = (double)pointN.Lat;
                            double lon = (double)pointN.Long;
                            if (k > 1)
                            {
                                lon = HemisphereCorrection(lon, lon1);
                            }
                            LatLongToPixelXY(lat, lon, m_MapZoomLevel, out pixelX, out pixelY);
                            ptArray[k - 1] = new Point(pixelX - m_NwX, pixelY - m_NwY);
                            lon1 = lon;
                        }
                        GraphicsPath polygonRegion = new GraphicsPath();
                        polygonRegion.AddPolygon(ptArray);
                        Region region = new Region(polygonRegion);

                        // check if polygon has more than one ring
                        // if it does 'cut' the others out as holes
                        if (j == 1)
                        {
                            finalregion = region;
                        }
                        else
                        {
                            // cut out from final region
                            if (finalregion != null)
                            {
                                finalregion.Exclude(region);
                            }
                        }

                        // draw the border around this polygon
                        // even if its a hole
                        Pen myPen = new Pen(CurrentLayerStyle.stroke);
                        myPen.Width = 1;
                        g.DrawPolygon(myPen, ptArray);
                    }
                }
                if (finalregion != null)
                {
                    // now we fill the final region once holes have been cut out of it
                    SolidBrush myBrush = new SolidBrush(valueFill);
                    g.FillRegion(myBrush, finalregion);
                }
            }
        }

        /// <summary>
        /// RenderMultiLinestring
        ///     Render a MultiLinestring to Graphics
        /// </summary>
        /// <param name="geo">SqlGeography geo</param>
        /// <param name="table">string layer table</param>
        /// <param name="g">Graphics used to draw to</param>
        /// <param name="id">int record id</param>
        /// <param name="valueFill">Color the stroke color style</param>
        private void RenderMultiLinestring(SqlGeography geo, Graphics g, Color valueFill)
        {
            SqlInt32 numGeometries = geo.STNumGeometries();
            if (numGeometries > 0)
            {
                for (int j = 1; j <= numGeometries; j++)
                {
                    SqlGeography geometryN = geo.STGeometryN(j);
                    SqlInt32 numRings = geometryN.NumRings();
                    if (numRings > 0)
                    {
                        for (int k = 1; k <= numRings; k++)
                        {
                            SqlGeography ringN = geometryN.RingN(k);
                            SqlInt32 numPoints = ringN.STNumPoints();
                            if (numPoints > 1)
                            {
                                Point[] ptArray = new Point[(int)geo.STNumPoints()];
                                double lon1 = 0.0;
                                for (int m = 1; m <= numPoints; m++)
                                {
                                    SqlGeography pointN = ringN.STPointN(m);
                                    double lat = (double)pointN.Lat;
                                    double lon = (double)pointN.Long;
                                    if (m > 1)
                                    {
                                        lon = HemisphereCorrection(lon, lon1);
                                    }
                                    LatLongToPixelXY(lat, lon, m_MapZoomLevel, out pixelX, out pixelY);
                                    ptArray[m - 1] = new Point(pixelX - m_NwX, pixelY - m_NwY);
                                    lon1 = lon;
                                }
                                GraphicsPath linePath = new GraphicsPath();
                                linePath.AddLines(ptArray);
                                Pen myPen = new Pen(valueFill);
                                myPen.Width = 2;
                                g.DrawPath(myPen, linePath);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// RenderMultiPolygon
        ///     Render a Multipolygon to Graphics
        /// </summary>
        /// <param name="geo">SqlGeography geo</param>
        /// <param name="table">string layer table</param>
        /// <param name="g">Graphics used to draw to</param>
        /// <param name="valueFill">Color the fill color style</param>
        private void RenderMultiPolygon(SqlGeography geo, Graphics g, Color valueFill)
        {
            SqlInt32 numGeometries = geo.STNumGeometries();
            if (numGeometries > 0)
            {
                for (int j = 1; j <= numGeometries; j++)
                {
                    SqlGeography geometryN = geo.STGeometryN(j);
                    SqlInt32 numRings = geometryN.NumRings();

                    // is just a single polygon now so call renderpolygon method
                    this.RenderPolygon(geometryN, g, valueFill);
                }
            }
        }

        /// <summary>
        /// RenderGeometryCollection
        ///     Render a GeometryCollection to Graphics
        /// </summary>
        /// <param name="geo">SqlGeography geo</param>
        /// <param name="table">string layer table</param>
        /// <param name="g">Graphics used to draw to</param>
        /// <param name="id">int record id</param>
        /// <param name="valueFill">Color the fill color style</param>
        private void RenderGeometryCollection(SqlGeography geo, Graphics g, Color valueFill)
        {
            SqlInt32 numGeometries = geo.STNumGeometries();
            if (numGeometries > 0)
            {
                for (int j = 1; j <= numGeometries; j++)
                {
                    SqlGeography geometryN = geo.STGeometryN(j);
                    SqlInt32 numRings = geometryN.NumRings();
                    if (numRings > 0)
                    {
                        for (int k = 1; k <= numRings; k++)
                        {
                            SqlGeography ringN = geometryN.RingN(k);
                            SqlInt32 numPoints = ringN.STNumPoints();
                            if (numPoints > 1)
                            {
                                double lon1 = 0.0;
                                Point[] ptArray = new Point[(int)numPoints];
                                for (int m = 1; m <= numPoints; m++)
                                {
                                    SqlGeography pointN = ringN.STPointN(m);
                                    double lat = (double)pointN.Lat;
                                    double lon = (double)pointN.Long;

                                    if (m > 1)
                                    {
                                        lon = HemisphereCorrection(lon, lon1);
                                    }

                                    LatLongToPixelXY(lat, lon, m_MapZoomLevel, out pixelX, out pixelY);
                                    ptArray[m - 1] = new Point(pixelX - m_NwX, pixelY - m_NwY);
                                    lon1 = lon;
                                }
                                GraphicsPath extRingRegion = new GraphicsPath();
                                extRingRegion.AddPolygon(ptArray);
                                Region region = new Region(extRingRegion);
                                g.FillRegion(new SolidBrush(valueFill), region);
                                Pen myPen = new Pen(CurrentLayerStyle.stroke);
                                myPen.Width = 1;
                                g.DrawPolygon(myPen, ptArray);
                            }
                        }
                    }
                }
            }
        }

        #endregion

        public class LoadStyle
        {
            public LoadStyle(
                Color stroke,
                Color fill,
                double opacity,
                int pointRadius,
                string valueCol,
                double maxValue,
                double skewFactor)
            {
                this.stroke = stroke;
                this.fill = fill;
                this.opacity = opacity;
                this.pointRadius = pointRadius;
                this.valueCol = valueCol;
                this.maxValue = maxValue;
                this.skewFactor = skewFactor;
            }

            public Color stroke { get; set; }
            public Color fill { get; set; }
            public double opacity { get; set; }
            public int pointRadius { get; set; }
            public string valueCol { get; set; }
            public double maxValue { get; set; }
            public double skewFactor { get; set; }
        }
    }
}